<?php

// @formatter:off
/**
 * @file WCService.abs.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-06-03 03:40:32 +0000 (Tue, 03 Jun 2014) $
 *
 * $Id: WCService.abs.php 112 2014-06-03 03:40:32Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/services/WCService.abs.php $
 */
// @formatter:on

abstract class WCService {
	const ErrorCodeOK = WC_HTTPERROR_OK;
	//
	// Protected class variables.
	protected static $_Debug = false;
	//
	// Protected variables.
	/**
	 * @var WCConnection Database pointer shortcut.
	 */
	protected $_db;
	/**
	 * @var string Database tables' prefix shortcut.
	 */
	protected $_dbprefix;
	protected $_count = 0;
	protected $_error = array(
		"code" => self::ErrorCodeOK,
		"message" => "Success",
		"info" => array(
			"file" => "",
			"line" => -1
		),
		"list" => array()
	);
	protected $_internal = false;
	protected $_method = "GET";
	protected $_methodHandler = "runGET";
	protected $_params = array();
	/**
	 * @var boolean
	 */
	protected $_readOnlyConflict = false;
	protected $_requiredParams = array(
		"url" => array(),
		"params" => array()
	);
	protected $_requiredPermissions = array(
		"to" => array(),
		"any" => array()
	);
	protected $_results = array();
	protected $_status = true;
	protected $_urlParams = array();
	protected $_useCache = false;
	//
	// Class methods.
	public function __construct() {
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $this->_db->prefix();
	}
	//
	// Public methods.
	public function __get($name) {
		return isset($this->$name) ? $this->_params[$name] : false;
	}
	public function __isset($name) {
		return isset($this->_params[$name]);
	}
	public function __set($name, $value) {
		$this->_params[$name] = $value;
		return $this->$name;
	}
	public function check() {
		if($this->ok()) {
			$this->checkParams();
			$this->checkPermissions();
			$this->checkReadOnly();
		}

		return $this->ok();
	}
	public function count() {
		return $this->_count;
	}
	public function getInterface() {
		$interface = new stdClass();
		$interface->class = get_called_class();
		$interface->cache = $this->_useCache;
		$interface->methods = array();
		$interface->readonlyconflict = $this->_readOnlyConflict;
		$interface->params = new stdClass();
		$interface->params->url = $this->_requiredParams["url"];
		$interface->params->params = $this->_requiredParams["params"];
		$interface->permissions = new stdClass();
		$interface->permissions->all = $this->_requiredPermissions["to"];
		$interface->permissions->any = $this->_requiredPermissions["any"];

		$methods = get_class_methods(get_called_class());
		foreach($methods as $method) {
			if(preg_match("/^(run)([A-Z]+)$/", $method, $parts)) {
				$interface->methods[] = $parts[2];
			}
		}

		foreach($interface->permissions as $type => $list) {
			foreach($list as $key => $value) {
				$interface->permissions->{$type}[$key] = array(
					"code" => $value,
					"name" => WCProfile::PermissionName($value)
				);
			}
		}

		return $interface;
	}
	public function isInternal() {
		return $this->_internal;
	}
	public function jsonResults() {
		$out = json_encode($this->results());
		if(json_last_error() != JSON_ERROR_NONE) {
			$this->_results = array();
			$this->setError(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "JSON Encode Error: [".json_last_error()."] ".json_last_error_msg());
			$out = json_encode($this->results());
		}

		return $out;
	}
	public function loadExternal() {
		$this->_method = $_SERVER["REQUEST_METHOD"];
		$this->_internal = false;

		return $this->load();
	}
	public function loadInternal($method = "GET", $urlParams = array(), $params = array()) {
		$this->_method = $method;
		$this->_internal = true;
		$this->_params = $params;
		$this->_urlParams = $urlParams;

		return $this->load();
	}
	public function ok() {
		return $this->_status;
	}
	public function prompt() {
		if(isset($_REQUEST["explainresults"])) {
			if($_REQUEST["explainresults"] == "print_r") {
				echo "<pre>".print_r($this->results(), true)."</pre>\n";
			} else {
				var_dump($this->results());
			}
		} else {
			echo $this->jsonResults();
		}
	}
	public function promptInterface() {
		echo json_encode($this->getInterface());
	}
	public function results() {
		$out = array();

		$this->checkGlobalError();

		global $wcDefaults;

		if(isset($_REQUEST["transaction"])) {
			$out["transaction"] = $_REQUEST["transaction"];
		}

		if($this->ok() || self::$_Debug || $wcDefaults["debug"]) {
			$out["data"] = $this->_results;
		}

		$out["status"] = $this->ok();
		$out["count"] = $this->count();
		$out["error"] = $this->_error;
		if(!$wcDefaults["debug"] && !self::$_Debug) {
			unset($out["error"]["info"]["caller"]);
			unset($out["error"]["info"]["file"]);
			unset($out["error"]["info"]["line"]);
			unset($out["error"]["info"]["list"]);
		}

		return $out;
	}
	public function run() {
		if($this->ok()) {
			if(!$this->isCached()) {
				$this->{$this->_methodHandler}();
				$this->saveCache();
			}
		}
	}
	public function setError($code, $message, $info = array()) {
		$bt = debug_backtrace();

		$caller = array_shift($bt);
		$info["file"] = isset($info["file"]) ? $info["file"] : $caller["file"];
		$info["line"] = isset($info["line"]) ? $info["line"] : $caller["line"];

		$caller = array_shift($bt);
		$info["caller"] = isset($info["caller"]) ? $info["caller"] : "{$caller["class"]}{$caller["type"]}{$caller["function"]}()";

		$error = array(
			"code" => $code,
			"message" => $message,
			"info" => $info
		);

		foreach($error as $key => $value) {
			$this->_error[$key] = $value;
		}

		$this->_error["list"][] = $error;

		$this->_status = $code == self::ErrorCodeOK;
		$this->_count = -1;
	}
	//
	// Protected methods.
	protected function cleanCache($selector, $forced = false) {
		//
		// Creating the statement to remove cache data.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__.($forced ? "[forced]" : "");
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from {$this->_dbprefix}services_cache\n";
			$query.= "where       srvc_url     = :url\n";
			$query.= " and        srvc_profile = :profile\n";
			$query.= " and        srvc_user    = :user\n";
			$query.= " and        srvc_extra   = :extra\n";
			if(!$forced) {
				$query.= " and        srvc_until   < now()\n";
			}

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		$stmt->execute($selector);
	}
	protected function checkGlobalError() {
		global $wcErrno;
		if($this->ok() && $wcErrno != 0) {
			global $wcError;
			$this->setError($wcErrno, $wcError);
		}
	}
	protected function checkParams() {
		foreach($this->_requiredParams["url"] as $paramName) {
			if(!isset($_REQUEST[$paramName])) {
				$this->setError(WC_HTTPERROR_BAD_REQUEST, "Required url parameter '{$paramName}' was not given");
			}
		}
		foreach($this->_requiredParams["params"] as $paramName) {
			if(!isset($this->_params[$paramName])) {
				$this->setError(WC_HTTPERROR_BAD_REQUEST, "Required parameter '{$paramName}' was not given");
			}
		}
	}
	protected function checkPermissions() {
		global $wcProfile;

		foreach($this->_requiredPermissions["to"] as $permission) {
			if(!$wcProfile->allowedTo($permission)) {
				$this->setError(WC_HTTPERROR_UNAUTHORIZED, "Insufficient privileges");
				break;
			}
		}
		if($this->ok()) {
			$allowed = false;
			foreach($this->_requiredPermissions["any"] as $permission) {
				$allowed = $allowed || $wcProfile->allowedTo($permission);
				if($allowed) {
					break;
				}
			}
			if(count($this->_requiredPermissions["any"]) > 0 && !$allowed) {
				$this->setError(WC_HTTPERROR_UNAUTHORIZED, "Insufficient privileges");
			}
		}
	}
	protected function checkReadOnly() {
		global $wcDefaults;

		if($this->_readOnlyConflict && $wcDefaults["read-only"]) {
			$this->setError(WC_HTTPERROR_FORBIDDEN, "ReadOnlyMode activated");
		}
	}
	protected function isCached() {
		$out = false;

		if($this->_useCache && $this->_methodHandler == "runGET") {
			//
			// Loading global pointers.
			global $wcServicesDefaults;
			global $wcProfile;
			global $wcUser;
			//
			// Checking if service cache system is activated.
			if(!isset($_REQUEST["explainresults"]) && $wcServicesDefaults["cache"]["active"] && !isset($_REQUEST["nocache"])) {
				//
				// Creating the statement to save cache
				// data @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "select  srvc_data as data\n";
					$query.= "from    {$this->_dbprefix}services_cache\n";
					$query.= "where   srvc_url     = :url\n";
					$query.= " and    srvc_profile = :profile\n";
					$query.= " and    srvc_user    = :user\n";
					$query.= " and    srvc_extra   = :extra\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				$params = array(
					":url" => md5($_SERVER["REQUEST_URI"]),
					":profile" => $wcProfile->code(),
					":user" => $wcUser ? $wcUser->id : 0,
					":extra" => ""
				);

				$this->cleanCache($params);

				if($stmt->execute($params) && $stmt->rowCount() > 0) {
					$row = $stmt->fetch();
					$this->_results = unserialize(gzuncompress($row["data"]));
					$out = true;
				}
			}
		}

		return $out;
	}
	protected function load() {
		$loaded = true;

		$this->_methodHandler = "run{$this->_method}";
		if(!method_exists($this, $this->_methodHandler)) {
			$this->setError(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "There's no handler for request method '{$this->_method}'");
		}

		if($this->ok()) {
			$paramsLoader = "loadParams".($this->isInternal() ? "Internal" : $this->_method);
			if(method_exists($this, $paramsLoader)) {
				$loaded = $this->{$paramsLoader}();
			}
		}

		return $loaded;
	}
	protected function loadParamsGET() {
		$out = true;

		if(isset($_REQUEST["params"])) {
			$this->_params = $this->loadJSONParams($_REQUEST["params"], $out);
		}

		return $out;
	}
	protected function loadJSONParams($params, &$ok) {
		$out = json_decode($params, true);
		$ok = true;

		if(json_last_error() != JSON_ERROR_NONE) {
			$this->setError(WC_HTTPERROR_INTERNAL_SERVER_ERROR, "JSON Decode Error: [".json_last_error()."] ".json_last_error_msg());
			$out = json_encode($this->results());
			$ok = false;
		}

		return $out;
	}
	protected function loadParamsPOST() {
		$out = false;

		if(isset($_POST["params"])) {
			$this->_params = $this->loadJSONParams($_POST["params"], $out);
		} else {
			$this->setError(WC_HTTPERROR_BAD_REQUEST, "POST parameter 'params' was not given");
		}

		return $out;
	}
	protected function saveCache() {
		if($this->_useCache && $this->_methodHandler == "runGET") {
			//
			// Loading global pointers.
			global $wcServicesDefaults;
			global $wcProfile;
			global $wcUser;
			//
			// Checking if service cache system is activated.
			if(!isset($_REQUEST["explainresults"]) && $wcServicesDefaults["cache"]["active"] && !isset($_REQUEST["nocache"])) {
				//
				// Creating the statement to save cache
				// data @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query.= "        into {$this->_dbprefix}services_cache (\n";
					$query.= "                srvc_url, srvc_data, srvc_until, srvc_profile, srvc_user, srvc_extra)\n";
					$query.= "        values (:url, :data, (now() + interval :until second), :profile, :user, :extra)\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				$params = array(
					":url" => md5($_SERVER["REQUEST_URI"]),
					":profile" => $wcProfile->code(),
					":user" => $wcUser ? $wcUser->id : 0,
					":extra" => ""
				);

				$this->cleanCache($params, true);

//			switch($expiration) {
//				case self::CacheTimeShort:
//					$params[":until"] = ceil($wcServicesDefaults["cache"]["expiration"] / 4);
//					break;
//				case self::CacheTimeHalf:
//					$params[":until"] = ceil($wcServicesDefaults["cache"]["expiration"] / 2);
//					break;
//				case self::CacheTimeMax:
//				default:
				$params[":until"] = $wcServicesDefaults["cache"]["expiration"];
//					break;
//			}
				$params[":data"] = gzcompress(serialize($this->_results));
				$stmt->execute($params);
			}
		}
	}
	//
	// Public class methods
	public static function ActivateDebugs() {
		self::$_Debug = true;
	}
}

class WCServiceDummy extends WCService {
	
}
